package gld.algo.park;

import gld.infra.*;

import java.util.*;

public class BasicTLC extends AbstractTLC {

	private Setting setting;

	private HashMap<Intersection, DecisionCycle> decisionCycles;

	public BasicTLC(Infrastructure i) throws InfraException {
		super(i);
		setting = new Setting();
	}
	
	@Override
	public void setInfrastructure(Infrastructure i){
		super.setInfrastructure(i);
		if (i != null)
			try {
				initDecisionCycles();
			} catch (InfraException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}

	/**
	 * Setting for BasicTLC
	 * 
	 * @author park
	 */
	class Setting {
		Integer defaultDecicionCyclePeriod = 20;

		public Integer getDefaultDecicionCyclePeriod() {
			return defaultDecicionCyclePeriod;
		}

		public void setDefaultDecicionCyclePeriod(Integer defaultDecicionCyclePeriod) {
			this.defaultDecicionCyclePeriod = defaultDecicionCyclePeriod;
		}

	}

	public class DecisionCycle {

		/** The whole period that this decision cycle is effective */
		Integer cyclePeriod;

		/** The counter of how long elapsed since the start of this decision cycle */
		Integer currentCycle;

		/** The stages that are inside the decision cycle */
		LinkedList<Stage> stages;
		
		/** The start times of stages. The length of this shall match stages */
		LinkedList<Integer> stageStartTimes;

		public DecisionCycle() {
			cyclePeriod = 0;
			currentCycle = 0;
			stages = new LinkedList<Stage>();
			stageStartTimes = new LinkedList<Integer>();
		}

		public Integer getCyclePeriod() {
			return cyclePeriod;
		}

		public Integer getCurrentCycle() {
			return currentCycle;
		}

		public void increaseCycle() {
			currentCycle++;
			// If we do the following, then the decisionCycle will never expire
			//currentCycle %= cyclePeriod;
		}

		public boolean isExpired() {
			return (currentCycle >= cyclePeriod);
		}

		/**
		 * Appends the given stage to the end of existing stages. It will increase
		 * cycle period.
		 * @param s
		 * @param duration If <= 0, the stage will get ignored.
		 */
		public void appendStage(Stage s, Integer duration) {
			if (duration <=0) return;
			stages.add(s);
			stageStartTimes.add(getCyclePeriod());
			cyclePeriod += duration;
		}

		/**
		 * If the current cycle matches starting time of any stage
		 * @return
		 */
		public boolean needsChangeStage() {
			for (int i : stageStartTimes) 
				if (currentCycle == i) return true;
			return false;
		}

		public Stage getCurrentStage() {
			for (int i = stages.size() -1; i >= 0; i--) {
				if (stageStartTimes.get(i) <= currentCycle)
					return stages.get(i);
			}
			return null; // Shall never happen
		}
		
		public String toString() {
			String s = "";
			for (int i=0; i < stages.size(); i++)
				s += "[" + stages.get(i) + ": " + stageStartTimes.get(i) + "]\n";
			return s;
		}
	}

	/**
	 * Assign each intersection with its own decision cycle
	 * @throws InfraException 
	 */
	protected void initDecisionCycles() throws InfraException {
		decisionCycles = new HashMap<Intersection, DecisionCycle>();
		for (Intersection i : getIntersections())
			initDecisionCycle(i);
	}

	protected DecisionCycle initDecisionCycle(Intersection i) throws InfraException {
		DecisionCycle dc = new DecisionCycle();
		LinkedList<Stage> stages = i.getStageConfig().getOrderedPossibleStages();
		Integer cyclePeriod = setting.getDefaultDecicionCyclePeriod();
		// Initially assign same period for each stage
		Integer stagePeriod = cyclePeriod / stages.size(); 
		for (Stage s : stages)
			dc.appendStage(s, stagePeriod);
		decisionCycles.put(i, dc);
		return dc;
	}

	@Override
	public HashMap<Junction, HashMap<Sign, Boolean>> getTLDecisions()
			throws InfraException {
		HashMap<Junction, HashMap<Sign, Boolean>> result = new HashMap<Junction, HashMap<Sign, Boolean>>();
		LinkedList<Intersection> is = getIntersections();
		for (Intersection i : is) {
			result.put(i.junction, getTLDecision(i));
		}

		return result;
	}

	/**
	 * Decides one tldecision of one intersection
	 * 
	 * @param i
	 * @return
	 * @throws InfraException
	 */
	protected HashMap<Sign, Boolean> getTLDecision(Intersection i)
			throws InfraException {
		// The result to return
		HashMap<Sign, Boolean> signs = new HashMap<Sign, Boolean>();

		DecisionCycle dc = decisionCycles.get(i);
		if (dc.isExpired()) {
			if (i.getJunction().getId()==8) {
				i.getClass();
			}
			LinkedList<Stage> stages = i.getStageConfig().getOrderedPossibleStages();
			// Calculate traffic, and assign a new stage setting

			// Get the sum of average traffic for each stage
			int startTime = java.lang.Math.max(getCurCycle()-200, 0);
			LinkedList<Double> stageTraffics = new LinkedList<Double>();
			double allTraffic = 0;
			
			if (i.getJunction().getId()==8) {
				i.getClass();
			}
			for (Stage s : stages) {
				double arrivalRateSum = 0;
				for (Drivelane lane : s.getEnabledDrivelanes()) {
					if (lane.getId() == 21) {
						lane.toString();
						LinkedList<Integer> r = lane.getArrivalTimeHistory();
						try {
							double rr = lane.getArrivalRate(1, 100);
						} catch (Exception e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
					try {
						arrivalRateSum += lane.getArrivalRate(startTime,getCurCycle());
					} catch (Exception e) {
						e.printStackTrace();
					}					
				}
				stageTraffics.add(arrivalRateSum);
				allTraffic += arrivalRateSum;
			}

			if (allTraffic == 0)
				dc = initDecisionCycle(i);
			else {
				// Assign stage timing according to ratio of traffic
				int cyclePeriod = setting.getDefaultDecicionCyclePeriod();
				dc = new DecisionCycle();			
				for (int i1=0; i1<stages.size(); i1++) 
					dc.appendStage(stages.get(i1), (int) (cyclePeriod * stageTraffics.get(i1) / allTraffic));
				// Don't forget to put it in the map
				decisionCycles.put(i, dc);
			}
		}

		if (dc.needsChangeStage()) {
			// Get tthe stage to change to
			Stage stage = dc.getCurrentStage();
			HashMap<Drivelane, Boolean> laneLights = stage.getDrivelaneLights();
			for (Map.Entry<Drivelane, Boolean> e : laneLights.entrySet()) {
				signs.put(e.getKey().getSign(), e.getValue());
			}
		}
		dc.increaseCycle();
		return signs;
	}
	


}
